package cn.b.sky.flowable.controller;


import cn.b.sky.common.constants.SystemConstant;
import cn.b.sky.common.enums.DwzCallbackType;
import cn.b.sky.common.exception.ImeException;
import cn.b.sky.common.model.DWZResult;
import cn.b.sky.flowable.constans.GlobalDefine;
import cn.b.sky.flowable.model.NodeDTO;
import cn.b.sky.flowable.model.ProcessModelCategory;
import cn.b.sky.flowable.model.ProcessModelParam;
import cn.b.sky.flowable.model.SkyService;
import cn.b.sky.flowable.service.DeploymentNodeService;
import cn.b.sky.flowable.service.ProcessModelCategoryService;
import cn.b.sky.flowable.service.ProcessParamService;
import cn.b.sky.flowable.service.SkyServiceService;
import cn.b.sky.flowable.utils.CustomProcessDiagramGenerator;
import cn.b.sky.form.model.BusinessField;
import cn.b.sky.form.service.BusinessFieldService;
import cn.b.sky.form.service.BusinessTypeService;
import cn.b.sky.user.SecurityContextUtil;
import cn.b.sky.user.controller.SecurityController;
import com.alibaba.fastjson.JSONObject;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.node.ObjectNode;

import lombok.extern.slf4j.Slf4j;
import org.apache.commons.io.IOUtils;
import org.apache.commons.lang3.StringUtils;
import org.flowable.bpmn.converter.BpmnXMLConverter;
import org.flowable.bpmn.model.*;
import org.flowable.editor.language.json.converter.BpmnJsonConverter;
import org.flowable.engine.RepositoryService;
import org.flowable.engine.impl.persistence.entity.ModelEntity;
import org.flowable.engine.repository.Deployment;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.multipart.MultipartFile;

import javax.annotation.Resource;
import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.xml.stream.XMLInputFactory;
import javax.xml.stream.XMLStreamReader;
import java.awt.*;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;

/**
 * <strong>Title : ModelController<br>
 * </strong> <strong>Package : cn.b.sky.flowable.controller<br>
 * </strong> <strong>Description : </strong> 流程模型controller<br>
 * <strong>Create on : 2014-10-15上午11:28:32<br>
 * </strong>
 * <p>
 * <strong>Copyright (c) 2014</strong>
 * <p>
 * 
 * @author lidapeng<br>
 * @version <strong>v1.0.0</strong><br>
 *          <br>
 *          <strong>修改历史:</strong><br>
 *          修改人 | 修改日期 | 修改描述<br>
 *          -------------------------------------------<br>
 *          <br>
 *          <br>
 */
@Controller
@RequestMapping(value = "/model/*")
@Slf4j
public class ModelController extends SecurityController {

    @Autowired
    private RepositoryService repositoryService;

    @Resource
    private ProcessModelCategoryService processModelCategoryService;

    @Resource
    private SkyServiceService skyService;

    @Resource
    private DeploymentNodeService deploymentNodeService;

    @Resource
    private ProcessParamService processParamService;

    @Resource
    private BusinessTypeService businessTypeService;
    @Resource
    private BusinessFieldService businessFieldService;

    /**
     * 模型列表
     */
    @RequestMapping("modelList")
    public String toModellist(Model model, String navTabId, String name) {
        List<ProcessModelCategory> pmc = processModelCategoryService.getProcessModelCategoryTree(this.getSiteCode());
        model.addAttribute("processModel", pmc);
        // 模型的名称用来确定焦点
        model.addAttribute("modelName", name);
        model.addAttribute(NAVTABID, navTabId);
        return "engine/model_list";
    }

    @RequestMapping("addModelDetailCategory")
    public String addModelDetailCategory(Model model, String navTabId) {
        model.addAttribute("businessTypes", businessTypeService.selectBusinessTypes());
        return "engine/addmodelcategory";
    }

    @RequestMapping("addModel")
    public String addModel(Model model, String category, String navTabId) {
        if (StringUtils.isBlank(category)) {
            throw ImeException.newException(SystemConstant.PARAMETERNULL);
        }
        model.addAttribute(NAVTABID, navTabId);
        model.addAttribute("category", category);
        return "engine/addmodel";
    }

    @RequestMapping("viewModelCategory")
    public String viewModelCategory(Model model, String category, String navTabId) {
        ProcessModelCategory pmc =
                processModelCategoryService.getProcessModelCategoryByCategory(category, this.getSiteCode());
        model.addAttribute(NAVTABID, navTabId);
        model.addAttribute("processModelCategory", pmc);
        model.addAttribute("businessTypes", businessTypeService.selectBusinessTypes());
        return "engine/viewmodelcategory";
    }

    @RequestMapping("getModelDetail")
    public String getModelDetail(Model model, String id) throws IOException {
        if (StringUtils.isBlank(id)) {
            throw ImeException.newException(SystemConstant.PARAMETERNULL);
        }
        ModelEntity modelEntity = (ModelEntity) repositoryService.createModelQuery().modelId(id).singleResult();

        // 没有流程图
        if (modelEntity.getEditorSourceExtraValueId() != null) {
            model.addAttribute("hasModelExtra", true);
            byte[] bytes = this.repositoryService.getModelEditorSource(id);
            JsonNode modelNode = new ObjectMapper().readTree(bytes);
            BpmnModel bpmnModel = new BpmnJsonConverter().convertToBpmnModel(modelNode);
            Map<String, GraphicInfo> graphicInfoMap = bpmnModel.getLocationMap();

            Point point = CustomProcessDiagramGenerator.getMinXAndMinY(bpmnModel);

            int minX = point.x;
            int minY = point.y;
            minX = minX <= 5 ? 5 : minX;
            minY = minY <= 5 ? 5 : minY;
            minX -= 5;
            minY -= 5;

            List<NodeDTO> nodeDtos = new ArrayList<>();
            for (Map.Entry<String, GraphicInfo> entry : graphicInfoMap.entrySet()) {
                String key = entry.getKey();
                GraphicInfo graphicInfo = entry.getValue();
                graphicInfo.setX(graphicInfo.getX() - minX);
                graphicInfo.setY(graphicInfo.getY() - minY);
                FlowElement flowElement = bpmnModel.getFlowElement(key);
                NodeDTO dto = convertNodeDto(graphicInfo, flowElement, key);
                nodeDtos.add(dto);
            }

            model.addAttribute("nodeDto", nodeDtos);
        }
        model.addAttribute("bpmnModel", modelEntity);
        model.addAttribute("userLanguage", SecurityContextUtil.getLanguageCode());
        return "engine/modelDtail";
    }

    private NodeDTO convertNodeDto(GraphicInfo graphicInfo, FlowElement flowElement, String id) {
        NodeDTO nodeDto = new NodeDTO();
        nodeDto.setX((int) graphicInfo.getX());
        nodeDto.setY((int) graphicInfo.getY());
        nodeDto.setWidth((int) graphicInfo.getWidth());
        nodeDto.setHeight((int) graphicInfo.getHeight());

        if (flowElement instanceof UserTask) {
            nodeDto.setType(GlobalDefine.WorkFlow.USAERTASK);
        }
        if (flowElement instanceof StartEvent) {
            nodeDto.setType(GlobalDefine.WorkFlow.STARTEVENT);
        }
        nodeDto.setId(id);
        nodeDto.setName(flowElement.getName());
        return nodeDto;
    }

    /**
     * 流程变量
     */
    @RequestMapping("paramList")
    public String toParamlist(Model model) {
        List<org.flowable.engine.repository.Model> list = repositoryService.createModelQuery().list();
        model.addAttribute("processModel", list);

        return "engine/param_list";
    }

    /**
     * 
     * <strong>Title : getModelEditorSourceExtra<br>
     * </strong> <strong>Description : </strong>获取流程模型图<br>
     * <strong>Create on : 2014-10-16下午3:53:15<br>
     * </strong>
     * <p>
     * void
     * 
     * @throws @author lidapeng<br>
     * @version <strong>v1.0.0</strong><br>
     *          <br>
     *          <strong>修改历史:</strong><br>
     *          修改人 | 修改日期 | 修改描述<br>
     *          -------------------------------------------<br>
     *          <br>
     *          <br>
     */
    @RequestMapping("getModelEditorSourceExtra")
    public void getModelEditorSourceExtra(String modelId, Model model, final HttpServletResponse response) {
        response.setContentType("image/png");
        response.setHeader("Pragma", "No-cache");
        response.setHeader("Cache-Control", "no-cache");
        response.setDateHeader("Expire", 0);
        try {
            ServletOutputStream sout = response.getOutputStream();
            byte[] buf = repositoryService.getModelEditorSourceExtra(modelId);
            sout.write(buf);
        } catch (IOException e) {
            log.error("{}", e);
        }
    }

    /**
     * 
     * @Title: isExitsModel @Description: 校验流程模型是否已经存在 @param key @param model @return DWZResult 返回类型 @throws
     */
    @RequestMapping("isExitsModel")
    @ResponseBody
    public boolean isExitsModel(String key, Model model) {
        if (!StringUtils.isBlank(key)) {
            long count = repositoryService.createModelQuery().modelKey(key).count();
            if (count > 0) {
                return false;
            }
        }
        return true;
    }

    @RequestMapping("isExitsModelCategory")
    @ResponseBody
    public boolean isExitsModelCategory(String category, Model model) {
        if (!StringUtils.isBlank(category)) {
            Integer count = processModelCategoryService.checkeCategoryIsExits(category, this.getSiteCode());
            if (count > 0) {
                return false;
            }
        }
        return true;
    }

    @RequestMapping("saveModelCategory")
    @ResponseBody
    public DWZResult saveModelCategory(ProcessModelCategory category, String navTabId, HttpServletRequest request,
                                       Model model) {
        if (category == null || StringUtils.isBlank(category.getCategory())) {
            return ajaxError(
                    getImeViewMessageI18n(SecurityContextUtil.getLanguageCode(), SystemConstant.PARAMETERNULL));
        }
        category.setTenantId(this.getSiteCode());
        processModelCategoryService.saveModelCategory(category);
        String forwardUrl = getForwardUrl(request) + "/model/modelList?navTabId=" + navTabId;
        return navTabAjaxDone(navTabId, null, DwzCallbackType.FORWARD, forwardUrl, null, category);
    }

    @RequestMapping("modifyModelCategory")
    @ResponseBody
    public DWZResult modifyModelCategory(ProcessModelCategory category, String navTabId, Model model) {
        if (category == null || StringUtils.isBlank(category.getCategory())) {
            return ajaxError(
                    getImeViewMessageI18n(SecurityContextUtil.getLanguageCode(), SystemConstant.PARAMETERNULL));
        }
        category.setTenantId(this.getSiteCode());
        processModelCategoryService.modifyModelCategory(category);
        return navTabAjaxDone(navTabId, null, null, null, null, null);
    }

    /**
     * 保存模型
     */
    @RequestMapping("saveModel")
    @ResponseBody
    public DWZResult saveModel(String name, String category, String description, String navTabId,
            HttpServletRequest request) {
        if (StringUtils.isBlank(category)) {
            return ajaxError(
                    getImeViewMessageI18n(SecurityContextUtil.getLanguageCode(), SystemConstant.PARAMETERNULL));
        }
        try {
            ObjectMapper objectMapper = new ObjectMapper();
            ObjectNode editorNode = objectMapper.createObjectNode();
            editorNode.put("id", "canvas");
            editorNode.put("resourceId", "canvas");
            ObjectNode stencilSetNode = objectMapper.createObjectNode();
            stencilSetNode.put("namespace", "http://b3mn.org/stencilset/bpmn2.0#");
            editorNode.set("stencilset", stencilSetNode);
            ModelEntity modelData = (ModelEntity) repositoryService.newModel();

            modelData.setName(name);
            modelData.setCategory(category);
            modelData.setMetaInfo(objectMapper.writeValueAsString(modelData));

            modelData.setTenantId(getSiteCode());

            // 保存模型
            repositoryService.saveModel(modelData);
            repositoryService.addModelEditorSource(modelData.getId(), editorNode.toString().getBytes("utf-8"));
            String forwardUrl = getForwardUrl(request) + "/model/modelList?navTabId=" + navTabId;
            return navTabAjaxDone(null, null, DwzCallbackType.FORWARD, forwardUrl, null, modelData);
        } catch (Exception e) {
            log.error("创建模型失败：", e);
            return ajaxError(getImeViewMessageI18n(SecurityContextUtil.getLanguageCode(), "failedOperation.M"));
        }
    }

    @RequestMapping("toImportModel")
    public String toImportModel(Model model, String category, String navTabId) {
        if (StringUtils.isBlank(category)) {
            throw ImeException.newException(SystemConstant.PARAMETERNULL);
        }
        model.addAttribute("category", category);
        model.addAttribute(NAVTABID, navTabId);
        return "engine/importmodel";
    }

    @RequestMapping("importModel")
    @ResponseBody
    public DWZResult importModel(@RequestParam("modelfile") MultipartFile modelfile, String navTabId, String category,
                                 HttpServletRequest request, Model model) {
        if (StringUtils.isBlank(category) || modelfile.isEmpty()) {
            log.error("文件为空，模型类型为空！");
            return ajaxError(
                    getImeViewMessageI18n(SecurityContextUtil.getLanguageCode(), SystemConstant.PARAMETERNULL));

        }
        String fileName = modelfile.getOriginalFilename();
        if (!(fileName.endsWith(".bpmn20.xml") || fileName.endsWith(".bpmn"))) {
            log.error("<h2>不能导入文件</h2><br/>仅支持.bpmn或.bpmn20.xml类型的文件！");
            return ajaxError(getImeViewMessageI18n(SecurityContextUtil.getLanguageCode(), "failedOperation.M"));
        }
        try {
            BpmnXMLConverter xmlConverter = new BpmnXMLConverter();
            XMLInputFactory xif = XMLInputFactory.newInstance();
            InputStreamReader in = new InputStreamReader(new ByteArrayInputStream(modelfile.getBytes()), "UTF-8");
            XMLStreamReader xtr = xif.createXMLStreamReader(in);
            BpmnModel bpmnModel = new BpmnXMLConverter().convertToBpmnModel(xtr);
            xmlConverter.convertToBpmnModel(xtr);
            if (bpmnModel.getMainProcess() == null || bpmnModel.getMainProcess().getId() == null) {
                log.error("导入失败，请确认文件中包含有效的BPMN定义！");
                return ajaxError(getImeViewMessageI18n(SecurityContextUtil.getLanguageCode(), "failedOperation.M"));
            } else {
                if (bpmnModel.getLocationMap().size() == 0) {
                    log.error("校验失败，请确认文件中包含BPMN DI信息!");
                    return ajaxError(getImeViewMessageI18n(SecurityContextUtil.getLanguageCode(), "failedOperation.M"));
                } else {
                    // 系统导出的流程标识含有流程模型类型以@_@分割
                    String[] processKeys = bpmnModel.getMainProcess().getId().split(SystemConstant.MIDDLE_SPLIT);
                    if (processKeys.length == 2) {
                        bpmnModel.getMainProcess().setId(processKeys[1]);
                    }
                    String processName = bpmnModel.getMainProcess().getName();
                    String processKey = bpmnModel.getMainProcess().getId();
                    if (StringUtils.isBlank(processName)) {
                        processName = processKey;
                    }
                    ModelEntity modelData = (ModelEntity) repositoryService.newModel();
                    modelData.setName(processName);
                    modelData.setCategory(category);
                    modelData.setKey(processKey);
                    modelData.setTenantId(getSiteCode());
                    modelData.setMetaInfo(JSONObject.toJSONString(modelData));
                    repositoryService.saveModel(modelData);

                    BpmnJsonConverter jsonConverter = new BpmnJsonConverter();
                    ObjectNode editorNode = jsonConverter.convertToJson(bpmnModel);

                    repositoryService.addModelEditorSource(modelData.getId(), editorNode.toString().getBytes("utf-8"));
                    String forwardUrl = getForwardUrl(request) + "/model/modelList.htm?navTabId=" + navTabId;
                    return dialogAjaxDone(navTabId, null, forwardUrl, modelData);
                }
            }
        } catch (Exception e) {
            String errorMsg = e.getMessage().replace(System.getProperty("line.separator"), "<br/>");
            log.error(errorMsg, e);
            return ajaxError(getImeViewMessageI18n(SecurityContextUtil.getLanguageCode(), "failedOperation.M"));
        }

    }

    /**
     * 根据Model部署流程
     */
    @ResponseBody
    @RequestMapping("deploy")
    public DWZResult deployModel(String modelId) {

        ModelEntity modelData = (ModelEntity) repositoryService.getModel(modelId);
        if (modelData.getEditorSourceExtraValueId() == null) {
            throw ImeException.newException("process_noimg.C,notDeploy.M");
        }
        try {
            ObjectNode modelNode = (ObjectNode) new ObjectMapper()
                    .readTree(this.repositoryService.getModelEditorSource(modelData.getId()));
            byte[] bpmnBytes = null;

            BpmnModel model = new BpmnJsonConverter().convertToBpmnModel(modelNode);

            bpmnBytes = new BpmnXMLConverter().convertToXML(model);
            String processName = modelData.getName() + ".bpmn20.xml";

            Deployment deployment = this.repositoryService.createDeployment().name(modelData.getName())
                    .tenantId(modelData.getTenantId()).addString(processName, new String(bpmnBytes, "UTF-8")).deploy();

            /*
             * List<ProcessDefinition> list = this.repositoryService.createProcessDefinitionQuery()
             * .deploymentId(deployment.getId()).list(); for (ProcessDefinition processDefinition : list) {
             * exportDiagramToFile(processDefinition); }
             */
            modelData.setDeploymentId(deployment.getId());
            repositoryService.saveModel(modelData);
            // 发布流程时复制节点配置信息
            deploymentNodeService.copyModelNodeToDeploymentNode(modelId, this.getSiteCode(), deployment.getId());
            return ajaxDone(null);
        } catch (Exception e) {
            log.error("根据模型部署流程失败：modelId={}", modelId, e);
            return ajaxError(getImeViewMessageI18n(SecurityContextUtil.getLanguageCode(), "failedOperation.M"));
        }
    }

    /**
     * 导出model的xml文件
     */
    @RequestMapping("exportModel")
    public void exportModel(String modelId, HttpServletResponse response) {

        ModelEntity modelData = (ModelEntity) repositoryService.getModel(modelId);
        if (modelData.getEditorSourceExtraValueId() == null) {
            throw ImeException.newException("process_noimg.C,notExport.M");
        }
        try {
            BpmnJsonConverter jsonConverter = new BpmnJsonConverter();
            JsonNode editorNode =
                    new ObjectMapper().readTree(repositoryService.getModelEditorSource(modelData.getId()));
            BpmnModel bpmnModel = jsonConverter.convertToBpmnModel(editorNode);
            String filename = bpmnModel.getMainProcess().getId() + ".bpmn20.xml";
            // 设置导出的流程id为category+@_@+id
            bpmnModel.getMainProcess()
                    .setId(modelData.getCategory() + SystemConstant.MIDDLE_SPLIT + bpmnModel.getMainProcess().getId());
            response.setContentType("application/octet-stream");
            response.addHeader("Content-Disposition", "attachment; filename=" + filename);
            BpmnXMLConverter xmlConverter = new BpmnXMLConverter();
            byte[] bpmnBytes = xmlConverter.convertToXML(bpmnModel);

            ByteArrayInputStream in = new ByteArrayInputStream(bpmnBytes);
            response.setBufferSize(bpmnBytes.length);
            IOUtils.copy(in, response.getOutputStream());

            response.flushBuffer();
        } catch (Exception e) {
            log.error("导出model的xml文件失败：modelId={}", modelId, e);
            throw ImeException.newException("failedOperation.M");
        }
    }

    @RequestMapping("deleteModel")
    @ResponseBody
    public DWZResult deleteModel(String modelId, String navTabId, HttpServletRequest request) {
        if (StringUtils.isBlank(modelId)) {
            return ajaxError(
                    getImeViewMessageI18n(SecurityContextUtil.getLanguageCode(), SystemConstant.PARAMETERNULL));
        }
        repositoryService.deleteModel(modelId);
        String forwardUrl = getForwardUrl(request) + "/model/modelList?navTabId=" + navTabId;
        return navTabAjaxDone(null, null, DwzCallbackType.FORWARD, forwardUrl, null, null);
    }

    @RequestMapping("properties/{modelId}")
    @ResponseBody
    public JSONObject getModelProperties(@PathVariable("modelId") String modelId) {

        List<SkyService> services = this.skyService.searchServiceByModelId(modelId);

        List<ProcessModelParam> params = new ArrayList<ProcessModelParam>();

        org.flowable.engine.repository.Model model = repositoryService.getModel(modelId);
        ProcessModelCategory pmc =
                processModelCategoryService.getProcessModelCategoryByCategory(model.getCategory(), this.getSiteCode());
        if (StringUtils.isBlank(pmc.getBusinessType())) {
            params = this.processParamService.getProcessModeParamByModelId(modelId);
        } else {
            List<BusinessField> fields =
                    businessFieldService.selectBusinessFields(getSiteCode(), pmc.getBusinessType());
            for (BusinessField field : fields) {
                ProcessModelParam param = new ProcessModelParam();
                param.setCode(field.getDbField());
                param.setName(field.getFieldLabel());
                params.add(param);
            }
        }

        JSONObject object = new JSONObject();
        object.put("services", services);
        object.put("params", params);
        return object;
    }
}
